Recipe Generator

ECE 5725 Spring 2024
A Project By Diane Pillsbury(dmp282) and Koushani Das(kd496)


Demonstration Video


Introduction

This project introduces an innovative application of the Raspberry Pi 4, paired with a PiCamera, designed to enhance the cooking experience by seamlessly integrating technology into the kitchen. The core functionality of this system lies in its ability to scan barcodes of various kitchen ingredients using the PiCamera. Once scanned, these ingredients are cataloged into a digital list. This list is then cross-referenced against a comprehensive database of recipes. The system smartly identifies and displays a step-by-step guide for the recipe that utilizes the highest number of available ingredients, ensuring an efficient and creative cooking process. This project not only simplifies meal preparation but also encourages the use of on-hand ingredients, reducing waste and promoting culinary creativity.


prototype

Project Objective:

  • Ingredient Recognition and Management: Accurately scan and catalog kitchen ingredients using the PiCamera, and effectively manage the data in a structured list.
  • Recipe Optimization: Match stored ingredients against a comprehensive recipe database to find and display recipes that use the most available items, enhancing user convenience and reducing food waste.
  • User Engagement: Provide an easy-to-use interface that displays step-by-step cooking instructions, encouraging culinary exploration and learning.

Design

Hardware

The hardware setup for this Raspberry Pi 4 project is straightforward and minimalistic, focusing on accessibility and ease of assembly. The primary components include:

  • Raspberry Pi 4: Serves as the central computing unit, running the Python-based application for barcode recognition and recipe display. Its powerful processor and ample memory handle the data processing and user interface smoothly.
  • PiCamera: This compact camera module is directly connected to the Raspberry Pi 4. It is crucial for capturing the images of the barcodes on kitchen ingredients, which are then processed by the Pyzbar library for barcode detection.
  • 3D Printed Stand: Custom-designed to hold both the Raspberry Pi 4 and the PiCamera securely in an optimal position. This stand ensures that the camera is steadily focused on the items for accurate scanning. The design was kept simple to facilitate easy replication and assembly.

The simplicity of the hardware design not only makes it user-friendly but also enhances the system's reliability by minimizing the potential for hardware-related issues. This setup is ideal for users who seek to integrate technology into everyday activities like cooking without the complexity of extensive hardware configurations.

Software

interface
Software System

The software component of this Raspberry Pi 4 project is designed to seamlessly integrate technology into the cooking process, enhancing the user experience through sophisticated barcode recognition and interactive recipe suggestions. An expanded overview of the software setup:

  • Barcode Recognition and Image Processing: The PiCamera captures images of kitchen ingredient barcodes. These images are then processed using OpenCV, a powerful image processing library. The preprocessing steps include converting images to grayscale to reduce complexity. Following preprocessing, the Pyzbar library is used to decode the barcodes. This library specializes in barcode detection and can decode multiple barcode formats efficiently. It extracts the textual data from the barcodes, which corresponds to specific ingredient identifiers.
  • Database Management: A MySQL database is utilized for its simplicity and efficiency in handling data queries within local storage environments. This database stores a comprehensive list of recipes and their corresponding ingredients. Python scripts manage database connectivity, executing SQL queries to match scanned ingredients with recipes in the database. This function is critical for dynamically suggesting recipes based on the current inventory of kitchen ingredients, enhancing the utility of the system in everyday cooking.
  • Graphical Interface with Pygame: Pygame is employed to develop the graphical user interface, which displays on a connected monitor or screen. The interface is designed for ease of use, featuring large, readable text and intuitive navigation controls. As new ingredients are scanned and recognized, the interface updates in real time to display corresponding recipes. This interactive element keeps users engaged and informed throughout the cooking process, allowing them to make immediate decisions based on the ingredients available.

The entire system is controlled through a main loop written in Python, which continuously checks for new images, processes them, and updates the display accordingly. The comprehensive and thoughtful software design ensures that the system not only performs its intended functions efficiently but also provides a user-friendly interface that enhances the overall cooking experience, making it more enjoyable and less cumbersome.

flow chart
UI Flow Chart

Testing

Throughout the testing process, print statements were used to debug, and the encapsulation of functionality into individual files allowed isolated testing to find issues easier.

MySQL Database

The first element of the Recipe Generator prototype we tested was the MySQL database and python functions which retrieve recipes from it. The database was tested by printing all entries in the “recipes” table using “SELECT * FROM recipes;” and “DESCRIBE recipes;” and verifying that all entries were as expected.

Next, we tested a Python file “database.py” by running a test script containing each of its functions, each of which connected to the MySQL database and executes a MySQL command (for example "SELECT * FROM recipes WHERE name LIKE '"+nameRec+"'") to access entries in the recipes table. Due to a lack of familiarity with SQL syntax, there were some issues retrieving the expected information, but those were quickly remedied by reviewing documentation.

Picamera and OpenCV

camera

One of the most significant roadblocks was getting the picamera to work. At first, even running raspistill in the command line did not work. After experimenting with multiple cameras and raspberry pis, it was determined that the issue was the cable connecting the Raspberry Pi to the camera, and after procuring a new cable and checking the camera settings, the camera worked as intended. With this resolved, we played around with OpenCV, the camera, and detection methods.

Ingredient Detection

linguine

Originally, we had attempted to do text recognition using the “tesseract” package. However, due to the wide variety of scripts and backgrounds on packaging, the tesseract text recognition did not work on packaging labels, only on clear text, even after preprocessing. As a result, we chose to move forward with barcode scanning using the “pyzbar” package instead, as barcodes were standardized and easier to detect. Barcode scanning proved more fruitful, and despite some issues with getting the camera to focus, barcode scanning was successful about 75% of the time. The exception to this figure was barcodes which were smaller or on very uneven surfaces, which were far more difficult to scan. An attempt was made at solving this problem using preprocessing, including grayscale filtering and skewing the image, but these did not improve scan success. Using machine learning to identify packaging was considered, but due to time constraints, the idea was tabled.

piTFT/GPIO User Interface

interface

Simultaneous to developing camera functionality, a user interface to display recipes in the MySQL database was in progress. This was incrementally tested independently of the camera module. First, the openRecipe function was tested with a generic recipe from the database to ensure it could access that recipe’s files and display them on the piTFT screen as expected. Next, once the camera was functional, another display for taking photos was developed, and tested in isolation to ensure that instructions and a live feed were displayed, and that an image was captured and displayed successfully. Finally, the display for ingredient detection, “processing,” was tested with a sample image to ensure that it correctly displayed the barcode’s text value.

In addition to testing the visual display, GPIO buttons to take photos, quit, and progress through the recipe were tested.

Complete System Testing

Once all other code was tested, we created a main program, “recipeSelector.py” which progresses through the display sequences. Once we had tested the entire program, taking a picture of a barcode, displaying its corresponding ingredient, and then opening the associated recipe, we added functionality for scanning multiple ingredients before generating a recipe. This required significant debugging, as well as an additional display mode “waitForButton” which provided the user time to press a button to stop taking photos and start recipe generation.


Result

As a proof of concept, all of our main goals were met. The prototype can successfully load recipes based on one or more ingredients’ barcodes, and provides an efficient user interface for kitchen use. The metric we used to choose recipes was the recipe with the highest number of ingredients, and did this successfully each time for recipes in our database. While the database of recipes and ingredients is limited at present, it can easily be expanded to accommodate more recipes.

As a final test, Diane used the Recipe Generator in an actual kitchen environment to cook a recipe, as displayed in the video. The prototype easily scanned barcodes and generated a recipe, and stepping through the ingredients on the inclined screen increased ease of reading the recipe while cooking.

The main issue was that the barcode scanner required very clear images, and some images were too blurry. The piCamera module has an adjustable focus, but still must be held at the proper distance for its focus, and getting the barcode in view can be a challenge with shaky hands.


Conclusion

Through the innovative use of the PiCamera and sophisticated image processing techniques, the project successfully automates the process of scanning kitchen ingredients and suggesting recipes based on available items. This system not only simplifies the culinary process but also encourages users to make the most of their pantry, potentially reducing food waste and inspiring culinary creativity. Users can easily interact with the system via a user-friendly interface, making it accessible to individuals of all tech-savviness levels. The simplicity of the hardware setup, involving minimal components like the Raspberry Pi 4, PiCamera, and a 3D-printed stand, ensures that the system is both affordable and easy to replicate. This accessibility promises wide applicability for similar solutions in other domains where barcode scanning and real-time data processing could enhance user experience and operational efficiency.

In conclusion, this project not only fulfills its initial objectives but also sets the stage for further innovations in smart kitchen technologies. Future enhancements could include integration with mobile devices for remote monitoring and control, addition of more complex algorithms for better prediction of user preferences, and expansion of the recipe database to cater to diverse dietary requirements and cuisines.

Currently, the prototype is very limited in the number of recipes and ingredients in its database, so the next step id to add more items, and possibly automate adding recipes and ingredients, as it is currently a tedious process. Additionally, adding object recognition would expand the number of ingredients which could be identified beyond those with barcodes (ex. produce).


Work Distribution

Diane

Diane

dmp282@cornell.edu

Did the programming, ideation and testing

Koushani

Koushani

kd496@cornell.edu

Experimented with detection methods, assisted with testing, acquired a 3D-printed frame.


Parts List

Total: $61


References

MySQL for Raspberry Pi
Interfacing MySQL with Python
Past Project Barcode Scanner
OpenCV Camera
Past Project using OpenCV and PyCamera
MySQL Documentation

Code Appendix

Github Repo

recipeSelector.py


// recipeSelector.py
from display import openRecipe, cameraMode, processing, waitForButton
from camTest import camera
import numpy as np
#import RPi.GPIO as GPIO

ingredients=np.array([])
loop=True


while(loop):
        # Take photos to identify ingredients
        cameraMode()
        # Process to get ingredient name
        ingred=processing()
        print(ingred)
        if ingred != "not found":
                if ingredients.size > 0:
                        ingredients=np.append(ingredients, ingred)
                else:
                        ingredients=np.array([ingred])
                print(ingredients)
        loop=waitForButton()

# Display recipe fetched from SQL database
if ingredients.size == 0:
        ingredients = np.array(["not found"])
openRecipe(ingredients)
}
              

database.py


//database.py
from mysql.connector import connect, Error
# not used: test function to see the databases my user has access to
def displayDatabases():
        # log into database
        try:
                with connect(
                host="localhost",
                user='pi',
                password='pi',
                ) as connection:
                        print(connection)
                        # display databases available/created
                        with connection.cursor() as cursor:
                                cursor.execute("SHOW DATABASES")
                                for db in cursor:
                                        print(db)

        except Error as e:
                print(e)

# Not used: test to see all recipes in SQL database "finalProject" "recipes" table
def displayRecipeTable():
        try:
                with connect(
                host="localhost",
                user='pi',
                password='pi',
                database='finalProject',
                ) as connection:
                        print(connection)
                        # display table
                        with connection.cursor() as cursor:
                                cursor.execute("SELECT * FROM recipes LIMIT 50")
                                result=cursor.fetchall()
                                for row in result:
                                        print(row)

        except Error as e:
                print(e)


# return pdf with title String nameRec
def findRecipe(nameRec):
        try:
                with connect(
                host="localhost",
                user='pi',
                password='pi',
                database='finalProject',
                ) as connection:
                        print(connection)
                        # display table
                        with connection.cursor() as cursor:
                                cursor.execute("SELECT * FROM recipes WHERE name LIKE '"+nameRec+"'")
                                result=cursor.fetchall()
                                for row in result:
                                        return(row)
        except Error as e:
                print(e)

# return pdf with ingredients specified by ingredient
def findRecipeByIngredient(ingredients):
        try:
                with connect(
                host="localhost",
                user='pi',
                password='pi',
                database='finalProject',
                ) as connection:
                        print(connection)
                        # display table
                        with connection.cursor() as cursor:
                                cursor.execute("SELECT * FROM recipes WHERE ingredients LIKE '%" + ingredients + "%'")
                                result=cursor.fetchall()
                                for row in result:
                                        return(row)
        except Error as e:
                print(e)
        

        

display.py


 //display.py
import webbrowser
from database import findRecipeByIngredient, displayRecipeTable, findRecipe
import pygame
import os
import RPi.GPIO as GPIO
import time
import cv2
from camTest import camera, barcode_image, preProcess
import subprocess
import numpy as np
#from camera import textID

pygame.init()

currStep = 0
takePicture= False
loop = True

#setup piTFT
os.putenv('SDL_VIDEODRIVER', 'fbcon')
os.putenv('SDL_FBDEV', '/dev/fb1')
os.putenv('SDL_MOUSEDRV', 'dummy')
os.putenv('SDL_MOUSEDEV', '/dev/null')
os.putenv('DISPLAY', '')

pygame.mouse.set_visible(False)

#setup next(27), photo (23) and quit(17) buttons
GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(27, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(23, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(22, GPIO.IN, pull_up_down=GPIO.PUD_UP)

def GPIO22_callback(channel):
        global loop
        loop = False

size = width, height = 320, 240
screen = pygame.display.set_mode(size)
font = pygame.font.Font(None, 25)
black = 0,0,0
cameraMode = False

def GPIO17_callback(channel):
                GPIO.cleanup()
                pygame.quit()

def GPIO23_callback(channel):
        if cameraMode:
                global takePicture
                takePicture= True
                camera('pic')
                print("took picture!")
#picture button
GPIO.add_event_detect(17, GPIO.FALLING, callback=GPIO17_callback)
GPIO.add_event_detect(23, GPIO.FALLING, callback=GPIO23_callback)
GPIO.add_event_detect(22, GPIO.FALLING, callback=GPIO22_callback)


def openRecipe(ingredients):
        notFound=False
        for i in range(ingredients.size):
                if ingredients[i] == "not found":
                        notFound=True
        if notFound:
                screen.fill(black)
                text_surface = font.render("No Ingredients input", True, (255, 255, 255))
                screen.blit(text_surface, (0,0))
                pygame.display.flip()
                GPIO.cleanup()
                return
       
       # find recipe based on ingredients
        numIngreds=np.array([["",0]])
        for ingred in ingredients:
                #print(ingred)
                name,recIngreds,path,maxSteps=findRecipeByIngredient(ingred)
                #print(name)
                contained=False
                for i in range(np.shape(numIngreds)[0]):
                        print(numIngreds[i,0])
                        if  name == numIngreds[i,0]:
                                numIngreds[1,i] = int(numIngreds[i,1])+1
                                contained=True
                if contained == False:
                        numIngreds = np.append(numIngreds, [[name,1]], axis=0)

        print(numIngreds)

        mostName=""
        mostIngreds=0
        for i in range(np.shape(numIngreds)[0]):
                if int(numIngreds[i,1]) > mostIngreds:
                        mostName=numIngreds[i,0]
                        
        #fetch from database
        name,recIngred,path,maxSteps=findRecipe(mostName)

        def GPIO27_callback(channel):
                #next step in recipe
                global currStep
                if currStep<(int(maxSteps)+1):
                        currStep +=1
                else:
                        pygame.quit()
                time.sleep(2)
        GPIO.add_event_detect(27, GPIO.FALLING, callback=GPIO27_callback)

        progress=True
        while (progress):
                if currStep==0:
                        img=pygame.image.load(path+'/'+name+'.png')
                elif currStep < (maxSteps+1):
                        img=pygame.image.load(path+'/step'+str(currStep)+'.png')
                else:
                        progress=False
                screen.fill(black)
                resized = pygame.transform.scale(img,(320,img.get_rect().height));
                screen.blit(resized, (0,0))
                pygame.display.flip()

        GPIO.cleanup()



def cameraMode():
        global cameraMode
        cameraMode = True
        global takePicture
        progress=True
        while (progress):
                time.sleep(0.1)
                print("camMode")
                if takePicture:
                        print("done")
                        img=pygame.image.load('capture.jpg')
                        screen.fill(black)
                        resized = pygame.transform.scale(img,(320,240));
                        screen.blit(resized, (0,0))
                        pygame.display.flip()
                        progress=False
                        cameraMode=False
                        takePicture=False
                else:
                        #print("cameraOn")
                        camera('display')
                        img=pygame.image.load('capture.jpg')
                        screen.fill(black)
                        resized = pygame.transform.scale(img,(320,240));
                        screen.blit(resized, (0,50))
                        text_surface = font.render("Press Button 23 to take a Photo", True, (255, 255, 255))
                        screen.blit(text_surface, (10, 30))
                        pygame.display.flip()

        time.sleep(3)


# preprocess, detect barcode, decode to ingredient name and return
def processing():
        print("process")
        preProcess()
        img,detected,text=barcode_image()
        #map barcode to ingredient
        if text=='4099100231953':
                text="linguine"
        elif text =="6009001014119" :
                text="pepper"
        else:
                text="not found"
        
        screen.fill(black)
        text_surface = font.render(str(text), True, (255, 255, 255))
        screen.blit(text_surface, (10, 30))
        pygame.display.flip()
        time.sleep(5)
        #print(text)
        return text

# Delays operation to allow user time to give input indicating all barcodes have been scanned
def waitForButton():
        global loop
        screen.fill(black)
        text_surface = font.render("Press 22 to stop camera", True, (255, 255, 255))
        screen.blit(text_surface, (10, 30))
        pygame.display.flip()
        time.sleep(5)
        return loop



        

camTest.py


//camtest.py
import cv2
import threading
from pyzbar import pyzbar
import numpy as np


command = None
cap = cv2.VideoCapture(0)
reqCommand = 'pic'

#Function to take a photo
def camera():
    while(True):
        ret, frame = cap.read()

        if ret:
            out = cv2.imwrite('capture.jpg', frame)
            #print('success')
            break


#Function to decode a barcode (adapted from Smart Fridge project)
def barcode_image():
    img = cv2.imread('capture.jpg')
    barcodes = pyzbar.decode(img)
    barcode_detected = False
    barcode_text = ''
    for barcode in barcodes:
        barcode_detected = True
        barcode_text = barcode.data.decode('utf-8')
        barcode_type = barcode.type
        print(f"Barcode: {barcode_text} )
    if barcode_detected == False:
        barcode_text="not detected"

    print(barcode_text)
    return img, barcode_detected, barcode_text

#Convert image to grayscale
def preProcess():
    img=cv2.imread("capture.jpg")
    img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    out = cv2.imwrite('capture.jpg', img_gray)


#Test function for debugging the picamera
def tester():
    from picamera import PiCamera
    from time import sleep

    camera = PiCamera()

    camera.start_preview()
    sleep(100)

    camera.stop_preview()